package org.dromara.business.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.*;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * 带重试机制的Web3j服务类
 * 提供对区块链操作的重试支持，提高网络不稳定时的成功率
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RetryableWeb3jService {

    private final Web3j web3j;

    /**
     * 获取交易信息（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public Optional<Transaction> getTransactionByHash(String txHash) throws IOException {
        log.debug("Fetching transaction by hash: {}", txHash);
        EthTransaction response = web3j.ethGetTransactionByHash(txHash).send();

        if (response.hasError()) {
            log.warn("Error fetching transaction {}: {}", txHash, response.getError().getMessage());
            throw new IOException("Transaction fetch error: " + response.getError().getMessage());
        }

        return response.getTransaction();
    }

    /**
     * 获取交易回执（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public Optional<TransactionReceipt> getTransactionReceipt(String txHash) throws IOException {
        log.debug("Fetching transaction receipt: {}", txHash);
        EthGetTransactionReceipt response = web3j.ethGetTransactionReceipt(txHash).send();

        if (response.hasError()) {
            log.warn("Error fetching transaction receipt {}: {}", txHash, response.getError().getMessage());
            throw new IOException("Transaction receipt fetch error: " + response.getError().getMessage());
        }

        return response.getTransactionReceipt();
    }

    /**
     * 获取账户余额（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public BigInteger getBalance(String address) throws IOException {
        log.debug("Fetching balance for address: {}", address);
        EthGetBalance response = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();

        if (response.hasError()) {
            log.warn("Error fetching balance for {}: {}", address, response.getError().getMessage());
            throw new IOException("Balance fetch error: " + response.getError().getMessage());
        }

        return response.getBalance();
    }

    /**
     * 获取当前区块号（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public BigInteger getBlockNumber() throws IOException {
        log.debug("Fetching current block number");
        EthBlockNumber response = web3j.ethBlockNumber().send();

        if (response.hasError()) {
            log.warn("Error fetching block number: {}", response.getError().getMessage());
            throw new IOException("Block number fetch error: " + response.getError().getMessage());
        }

        return response.getBlockNumber();
    }

    /**
     * 获取区块信息（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public Optional<EthBlock.Block> getBlockByNumber(BigInteger blockNumber, boolean fullTransactionObjects) throws IOException {
        log.debug("Fetching block by number: {}", blockNumber);
        EthBlock response = web3j.ethGetBlockByNumber(
            org.web3j.protocol.core.DefaultBlockParameter.valueOf(blockNumber),
            fullTransactionObjects
        ).send();

        if (response.hasError()) {
            log.warn("Error fetching block {}: {}", blockNumber, response.getError().getMessage());
            throw new IOException("Block fetch error: " + response.getError().getMessage());
        }

        return Optional.ofNullable(response.getBlock());
    }

    /**
     * 获取合约代码（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public String getCode(String address) throws IOException {
        log.debug("Fetching code for address: {}", address);
        EthGetCode response = web3j.ethGetCode(address, DefaultBlockParameterName.LATEST).send();

        if (response.hasError()) {
            log.warn("Error fetching code for {}: {}", address, response.getError().getMessage());
            throw new IOException("Code fetch error: " + response.getError().getMessage());
        }

        return response.getCode();
    }

    /**
     * 获取客户端版本（带重试）
     */
    @Retryable(retryFor = {IOException.class}, maxAttempts = 3,
               backoff = @Backoff(delay = 1000, multiplier = 2))
    public String getClientVersion() throws IOException {
        log.debug("Fetching Web3 client version");
        Web3ClientVersion response = web3j.web3ClientVersion().send();

        if (response.hasError()) {
            log.warn("Error fetching client version: {}", response.getError().getMessage());
            throw new IOException("Client version fetch error: " + response.getError().getMessage());
        }

        return response.getWeb3ClientVersion();
    }

    /**
     * 异步获取交易信息
     */
    public CompletableFuture<Optional<Transaction>> getTransactionByHashAsync(String txHash) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return getTransactionByHash(txHash);
            } catch (IOException e) {
                log.error("Failed to fetch transaction {} async", txHash, e);
                return Optional.empty();
            }
        });
    }

    /**
     * 异步获取交易回执
     */
    public CompletableFuture<Optional<TransactionReceipt>> getTransactionReceiptAsync(String txHash) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return getTransactionReceipt(txHash);
            } catch (IOException e) {
                log.error("Failed to fetch transaction receipt {} async", txHash, e);
                return Optional.empty();
            }
        });
    }

    /**
     * 批量获取交易信息
     */
    public CompletableFuture<java.util.List<Optional<Transaction>>> getBatchTransactions(java.util.List<String> txHashes) {
        java.util.List<CompletableFuture<Optional<Transaction>>> futures = txHashes.stream()
            .map(this::getTransactionByHashAsync)
            .toList();

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .toList());
    }

    /**
     * 检查Web3j连接状态
     */
    public boolean isConnected() {
        try {
            getClientVersion();
            return true;
        } catch (IOException e) {
            log.warn("Web3j connection check failed: {}", e.getMessage());
            return false;
        }
    }
}
